#include "Scene.h"
#include <cassert>
#include <cmath>

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define POS(a) MAX(a, 0)

using namespace LA;
using namespace CG;

/**
 * @file Scene.cpp
 * @author xingyifan
 * @date 2022-12-31 23:44
 *
 * @description: 基本创建操作
 */

Scene::Scene(CFrameWnd *pWnd, Camera *camera) : m_pWnd(pWnd), m_camera(camera)
{
    // 设置 OpenGL 绘图设备
    PIXELFORMATDESCRIPTOR pfd =
        {
            sizeof(PIXELFORMATDESCRIPTOR), // pfd 结构大小
            1,                             // 版本号
            PFD_DRAW_TO_WINDOW |           // 支持窗口绘图
                PFD_SUPPORT_OPENGL |       // 支持 OpenGL
                PFD_DOUBLEBUFFER,          // 双缓冲模式
            PFD_TYPE_RGBA,                 // RGBA 绘图模式
            24,                            // 24 位颜色深度
            0, 0, 0, 0, 0, 0,              // 忽略颜色位
            0,                             // 没有非透明度缓存
            0,                             // 忽略移位位
            0,                             // 无累计缓存
            0, 0, 0, 0,                    // 忽略累计位
            32,                            // 32 位深度缓存
            0,                             // 无模板缓存
            0,                             // 无辅助缓存
            PFD_MAIN_PLANE,                // 主层
            0,                             // 保留
            0, 0, 0                        // 忽略层，可见性和损毁掩模
        };

    CClientDC dc(pWnd);
    int pf = ChoosePixelFormat(dc.m_hDC, &pfd);
    BOOL rt = SetPixelFormat(dc.m_hDC, pf, &pfd);

    // 获得当前 RC，同时设为当前 RC
    m_hglrc = wglCreateContext(dc.m_hDC);

    // 设置当前 RC
    CPaintDC CPdc(pWnd);
    wglMakeCurrent(CPdc.m_hDC, m_hglrc);
}

Scene::~Scene()
{
    for (auto it = m_mList.begin(); it != m_mList.end(); ++it)
        if ((*it) != nullptr)
            delete (*it);
    for (auto it = m_lList.begin(); it != m_lList.end(); ++it)
        if ((*it) != nullptr)
            delete (*it);
    for (auto it = m_csList.begin(); it != m_csList.end(); ++it)
        if ((*it) != nullptr)
            delete (*it);
    for (auto it = m_dsList.begin(); it != m_dsList.end(); ++it)
        if ((*it) != nullptr)
            delete (*it);

    if (wglGetCurrentContext() != nullptr)
        wglMakeCurrent(nullptr, nullptr);

    // 销毁 RC
    if (m_hglrc != nullptr)
    {
        wglDeleteContext(m_hglrc);
        m_hglrc = nullptr;
    }
}

void Scene::LoadRC()
{
    // 设置当前 RC
    CPaintDC CPdc(m_pWnd);
    wglMakeCurrent(CPdc.m_hDC, m_hglrc);
}

void Scene::RemoveRC()
{
    // 取消当前 RC
    wglMakeCurrent(nullptr, nullptr);
}

/**
 * @file Scene.cpp
 * @author xingyifan
 * @date 2023-01-01 16:30
 *
 * @description: 网格创建和销毁
 */

int Scene::CreateMesh()
{
    Mesh *mesh = new Mesh;
    m_mList.push_back(mesh);

    return m_mList.size() - 1;
}

bool Scene::DeleteMesh(int i)
{
    if (i < m_mList.size() && m_mList[i] != nullptr)
    {
        delete m_mList[i];
        m_mList[i] = nullptr;
        m_mList.erase(m_mList.begin() + i);
        return true;
    }
    return false;
}

/**
 * @file Scene.cpp
 * @author xingyifan
 * @date 2023-01-01 16:18
 *
 * @description: 注册网格信息
 */

void Scene::RegisterMesh(int i)
{
    Mesh *mesh = m_mList[i];

    // 计算网格包围盒
    mesh->GetBox();

    // 如果没有法向，就插值计算顶点法向
    if (!mesh->HasVertexNormal())
        for (int i = 0; i < mesh->VertexNum(); i++)
            mesh->m_nList.push_back(mesh->Gouraud(mesh->m_vList[i]));
}

/**
 * @file Scene.cpp
 * @author xingyifan
 * @date 2023-01-01 14:16
 *
 * @description: 设置光照
 */

int Scene::CreateLighting(coord4f pos, float L[][3], float *reduce)
{
    Lighting *light = new Lighting;
    m_lList.push_back(light);

    // 设置光源位置
    if (pos.w == 1)
        light->SetPoint(pos.x, pos.y, pos.z);
    else
        light->SetParallel(pos.x, pos.y, pos.z);

    // 设置颜色和衰减因子
    light->SetLighting(L);
    light->SetReduce(reduce[0], reduce[1], reduce[2]);

    return m_lList.size() - 1;
}

bool Scene::DeleteLighting(int i)
{
    if (i < m_lList.size() && m_lList[i] != nullptr)
    {
        delete m_lList[i];
        m_lList[i] = nullptr;
        m_lList.erase(m_lList.begin() + i);
        return true;
    }
    return false;
}

/**
 * @file Scene.cpp
 * @author xingyifan
 * @date 2023-01-11 11:49
 *
 * @description: 创建交互
 */

int Scene::CreateClick(ClickType type)
{
    ClickScene *cs = nullptr;
    switch (type)
    {
    case ClickType::Click_Mesh:
    {
        cs = new ClickMesh;
        break;
    }
    }
    m_csList.push_back(cs);
    return m_csList.size() - 1;
}

int Scene::CreateDraw(DrawType type)
{
    DrawScene *ds = nullptr;
    switch (type)
    {
    case DrawType::Draw_Axis:
    {
        ds = new DrawAxis;
        break;
    }
    case DrawType::Draw_Mesh:
    {
        ds = new DrawMesh;
        break;
    }
    case DrawType::Draw_Box:
    {
        ds = new DrawBox;
        break;
    }
    }
    m_dsList.push_back(ds);
    return m_dsList.size() - 1;
}

bool Scene::DeleteClick(int i)
{
    if (i < m_csList.size() && m_csList[i] != nullptr)
    {
        delete m_csList[i];
        m_csList[i] = nullptr;
        m_csList.erase(m_csList.begin() + i);
        return true;
    }
    return false;
}

bool Scene::DeleteDraw(int i)
{
    if (i < m_dsList.size() && m_dsList[i] != nullptr)
    {
        delete m_dsList[i];
        m_dsList[i] = nullptr;
        m_dsList.erase(m_dsList.begin() + i);
        return true;
    }
    return false;
}

/**
 * @file Scene.cpp
 * @author xingyifan
 * @date 2023-01-11 00:30
 *
 * @description: 重绘场景
 */

void Scene::Redraw()
{
    m_pWnd->InvalidateRect(NULL, FALSE);
}

/**
 * @file Scene.cpp
 * @author xingyifan
 * @date 2023-01-11 00:27
 *
 * @description: 计算光照，同时绘制场景
 */

void Scene::Draw()
{
    // 开启深度测试，清除缓冲
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    point3f viewer = m_camera->m_viewer;

    // 计算网格光照
    for (auto it = m_mList.begin(); it != m_mList.end(); ++it)
    {
        Mesh *mesh = *it;

        // 清空之前顶点颜色
        mesh->m_cList.clear();

        // 计算光照
        for (int i = 0; i < mesh->VertexNum(); i++)
        {
            vertex3f *nv = mesh->m_vList[i];
            point3f np = nv->pos;

            // 三个关键方向 l n v，n
            vector3f n = mesh->m_nList[i];
            vector3f l;
            vector3f v;

            // 初始化光照
            rgb_space color = {0, 0, 0};
            float alpha = mesh->m_material.alpha;
            float d = 0;

            // 遍历所有光照模型计算
            for (int i = 0; i < m_lList.size(); i++)
            {
                Lighting *light = m_lList[i];

                // 判断光源是否是平行光源
                if (light->l.w == 1)
                {
                    l = normalize(point3f(light->l.x, light->l.y, light->l.z) - np);
                    v = normalize(viewer - np);

                    // 计算光源距离，a+bd+cd^2 得到衰减因子
                    d = distance(np, point3f(light->l.x, light->l.y, light->l.z));
                    d = 1.0 / (light->a + light->b * d + light->c * d * d);

                    // 再计算角度衰减，如果夹角是负数，那么直接衰减为零
                    // 注意我们之前反转了 l，所以点积后要取相反数
                    float cos_alpha = -dot(light->center, l);

                    // 夹角超过圆锥角，则归零
                    if (cos_alpha < cos(light->theta))
                        cos_alpha = 0;

                    d *= pow(cos_alpha, light->al);
                }
                else
                {
                    l = normalize(vector3f(-light->l.x, -light->l.y, -light->l.z));
                    v = normalize(viewer - np);

                    // 平行光源不衰减
                    d = 1;
                }

                // 计算半角向量
                vector3f h = normalize(l + v);

                // 根据夹角计算衰减系数：注意先对夹角余弦取正，然后再作用指数 alpha
                float l_dot_n = POS(dot(l, n));
                float n_dot_h = pow(POS(dot(n, h)), alpha);

                // 顺序 a,d,s
                rgb_space I;
                I.r = mesh->m_material.R[0][0] * light->L[0][0] +
                      (mesh->m_material.R[1][0] * light->L[1][0] * l_dot_n +
                       mesh->m_material.R[2][0] * light->L[2][0] * n_dot_h) *
                          d;
                I.g = mesh->m_material.R[0][1] * light->L[0][1] +
                      (mesh->m_material.R[1][1] * light->L[1][1] * l_dot_n +
                       mesh->m_material.R[2][1] * light->L[2][1] * n_dot_h) *
                          d;
                I.b = mesh->m_material.R[0][2] * light->L[0][2] +
                      (mesh->m_material.R[1][2] * light->L[1][2] * l_dot_n +
                       mesh->m_material.R[2][2] * light->L[2][2] * n_dot_h) *
                          d;

                // 防止颜色越界
                I.r = MIN(MAX(I.r, 0), 1);
                I.g = MIN(MAX(I.g, 0), 1);
                I.b = MIN(MAX(I.b, 0), 1);

                // 颜色叠加
                color = color + I;
            }

            // 添加颜色
            mesh->m_cList.push_back(color);
        }
    }

    // 调用列表中的绘制函数
    for (auto it = m_dsList.begin(); it != m_dsList.end(); it++)
        (*it)->draw(this);

    // 刷新缓冲
    glFlush();

    // 与 DC 交换缓冲，这是图像显示的最后一步
    CPaintDC dc(m_pWnd);
    SwapBuffers(dc.m_hDC);
}

/**
 * @file Scene.cpp
 * @author xingyifan
 * @date 2023-01-11 11:37
 *
 * @description: 点击操作
 */

void Scene::Click()
{
    // 调用列表中的点击函数
    for (auto it = m_csList.begin(); it != m_csList.end(); it++)
        (*it)->react(this);
}